from java.lang import System #IGNORE:F0401
from javax.servlet.http import HttpServlet,  HttpServletResponse #IGNORE:F0401
from javax.xml.transform.stream import StreamResult #IGNORE:F0401
from javax.xml.transform.dom import DOMSource #IGNORE:F0401
from javax.xml.parsers import DocumentBuilderFactory #IGNORE:F0401
from javax.naming import InitialContext #IGNORE:F0401
from javax.xml.transform import TransformerFactory #IGNORE:F0401
from org.openid4java.discovery import DiscoveryException #IGNORE:F0401
from org.openid4java.consumer import ConsumerManager #IGNORE:F0401
import org.openid4java.consumer
from java.net import InetAddress #IGNORE:F0401
from org.openid4java.discovery.yadis import YadisResolver #IGNORE:F0401
from org.openid4java import OpenIDException #IGNORE:F0401

def document_builder():
    factory = DocumentBuilderFactory.newInstance()
    factory.setNamespaceAware(True)
    return factory.newDocumentBuilder()

class Invocation: #IGNORE:R0902
    def __init__(self, request, response, static_element, services): 
        self.request = request
        self.response = response
        self.services = services
        self.static_element = static_element
        self.__con__ = None
        
    def server_authority(self):
        server_authority = self.request.getServerName()
        scheme = self.request.getScheme()
        server_port = self.request.getServerPort()
        if (scheme == 'https' and server_port != 443) or \
                (scheme == 'http' and server_port != 80):
            server_authority += ':' + str(server_port)
        return server_authority
     
    def send_response(self, status, document): #IGNORE:R0914
        path_info = self.request.getPathInfo()
        if path_info[-1:] != '/':
            path_info = path_info + '/'
        if document == None:
            document = self.create_document()
        response_element = document.createElement('response')
        document.documentElement.appendChild(response_element)
        response_element.setAttribute('status-code', str(status))
        request_element = document.createElement('request')
        document.documentElement.appendChild(request_element)
        server_name = self.request.getServerName()
        server_port = self.request.getServerPort()
        request_element.setAttribute('server-name', server_name)
        request_element.setAttribute('server-port', str(server_port))
        request_element.setAttribute('server-authority',
                                     self.server_authority())
        request_element.setAttribute('context-path',
                                     self.request.getContextPath())
        for control_name in self.request.getParameterMap():
            control_element = document.createElement('control')
            request_element.appendChild(control_element)
            control_element.setAttribute('name', control_name)
            control_element.setTextContent(
                        self.request.getParameter(control_name))
        for attribute_name in self.request.getSession().getAttributeNames():
            if attribute_name.startswith('openid-login-'):
                auth_success = self.request.getSession().getAttribute(
                                                attribute_name)
                openid_element = document.createElement('openid')
                request_element.appendChild(openid_element)
                openid_element.setAttribute('op-local-id', auth_success[0])
                openid_element.setAttribute('op-endpoint', auth_success[1])
        self.response.setCharacterEncoding("UTF-8")
        now = System.currentTimeMillis()
        if not self.response.containsHeader('Expires'):
            self.response.setDateHeader('Expires', now)
        self.response.setDateHeader('Date', now)
        self.response.setStatus(status)
        resource_stream = self.services.servlet_context. \
                getResourceAsStream('/WEB-INF/' + self.static_element +
                path_info + 'template.xsl')
        if resource_stream is None:
            resource_stream = self.services.servlet_context. \
                getResourceAsStream('/WEB-INF/' + self.static_element +
                '/template.xsl')
        template_doc = document_builder().parse(resource_stream)
        stylesheet_element = template_doc.getDocumentElement()
        output_element = stylesheet_element.getElementsByTagName(
                                    'xsl:output').item(0)
        content_type = output_element.getAttribute('media-type')
        self.response.setHeader('Content-type', content_type)
        if self.request.getMethod() != 'HEAD':
            self.services.transformer(DOMSource(template_doc)).transform(
                    DOMSource(document),
                    StreamResult(self.response.getOutputStream()))
        
    def send_not_found(self, document):
        self.send_response(HttpServletResponse.SC_NOT_FOUND, document)
    
    def send_internal_server_error(self, document=None):
        self.send_response(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                                  document)
    
    def send_ok(self, document=None):
        self.send_response(HttpServletResponse.SC_OK, document)
    
    def send_method_not_allowed(self, document=None):
        self.send_response(HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                                  document)
    def send_user_error(self, document=None):
        self.send_response(418, document)

    def send_moved_permanently(self, location, document=None):
        self.response.setHeader('Location', location)
        self.send_response(HttpServletResponse.SC_MOVED_PERMANENTLY, document)

    def send_see_other(self, location, document=None):
        self.response.setHeader('Location', location)
        self.send_response(HttpServletResponse.SC_SEE_OTHER, document)
                
    def create_document(self, root_element_name=None):
        if root_element_name is None:
            root_element_name = self.services.root_element_name
        document = document_builder().newDocument()
        document_element = document.createElement(root_element_name)
        document.appendChild(document_element)
        return document
    
    def database_connection(self):
        if self.__con__ == None:
            self.__con__ = self.services.database_connection()
        return self.__con__
    
    def get_string(self, name):
        return self.request.getParameter(name)
    
    def has_parameter(self, name):
        return self.get_string(name) is not None
    
    def close(self):
        if self.__con__ is not None:
            self.__con__.close()
    
    def openid_discovered(self, user_supplied_id):
        try:
            discoveries = self.services.consumer_manager.discover(
                    user_supplied_id)
            if len(discoveries) == 0:
                raise UserError('No OP Endpoints discovered')
            discovered = self.services.consumer_manager.associate(discoveries)
        except DiscoveryException, detail:
            message = detail.getMessage()
            cause = detail.getCause()
            if cause is not None:
                message = message + cause.getClass().getName() + ' ' + cause.getMessage()
            raise UserError(message)
        return discovered
                
    def openid_authenticate(self, user_supplied_id):
        return_url = self.request.getRequestURL().append(
                                        'openid-return/').toString()
        try:
            discovered = self.openid_discovered(user_supplied_id)
            self.request.getSession().setAttribute("openid-disco",
                                                      discovered)
            auth_req = self.services.consumer_manager.authenticate(discovered,
                            return_url)
        except DiscoveryException, detail:
            raise UserError(detail.getMessage())
        self.send_see_other(auth_req.getDestinationUrl(True))

class Urlable:
    def __init__(self, parent_urlable):
        self.parent_urlable = parent_urlable
        
    def child(self, path_element): #pylint: disable-msg=R0201,W0613
        return Static()

    def http_get(self, inv): #pylint: disable-msg=R0201
        return inv.send_method_not_allowed()

    def http_post(self, inv): #pylint: disable-msg=R0201
        return inv.send_method_not_allowed()

class JystServlet(HttpServlet, Urlable):
    def __init__(self):
        Urlable.__init__(self, None)
        self.services = None
        self.virtual_urlable = None
        self.hosts = []
         
    def initialize(self, config, root_element, db_name, virtual_urlable=None):
        if config is None:
            HttpServlet.init(self)
        else:
            HttpServlet.init(self, config)
        servlet_config = self.getServletConfig() #IGNORE:E1101
        self.servlet_context = servlet_config.getServletContext() #IGNORE:W0201
        self.services = Services(root_element, db_name, self.servlet_context) 
        self.virtual_urlable = virtual_urlable
        for host in servlet_config.getInitParameter('hosts').split(' '):
            self.hosts.append(host)
            self.hosts.append(host + '.')
            self.hosts.append(InetAddress.getByName(host).getHostAddress())
        Urlable.__init__(self, None)

    def service(self, request, response):
        try:
            server_name = request.getServerName()
            if server_name in self.hosts:
                urlable = self
                static_element = 'server'
            else:
                urlable = self.virtual_urlable
                static_element = 'virtual'
            for path_element in request.getPathInfo().replace('/', ' ').split():
                urlable = urlable.child(path_element)
            inv = Invocation(request, response, static_element,
                             self.services)
            request_url = request.getRequestURL().toString()
            if request_url[-1:] != '/':
                raise MovedPermanentlyError(request_url + '/')
            method = request.getMethod()
            if method == 'GET' or method == 'HEAD':
                urlable.http_get(inv)
            elif method == 'POST':
                urlable.http_post(inv)
            else:
                raise UserError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
        except UserError, error:
            inv.send_user_error(error.make_document(inv.create_document()))
        except NotFoundError, error:
            inv.send_not_found(error.make_document(inv.create_document()))
        except MovedPermanentlyError, error:
            inv.send_moved_permanently(error.location)
        except InternalServerErrorError, error:
            inv.send_internal_server_error(
                        error.make_document(inv.create_document()))
        inv.close()

class Services: #IGNORE:R0903
    def __init__(self, root_element_name, database_name, servlet_context):
        self.root_element_name = root_element_name
        self.database_name = database_name
        self.servlet_context = servlet_context
        self.context = InitialContext()
        self.transformer_factory = TransformerFactory.newInstance()
        self.data_source = self.context.lookup('java:/comp/env/jdbc/' +
                                               self.database_name)
        if self.data_source is None:
            raise UserError("Data source not found!")
        self.consumer_manager = ConsumerManager()
        self.consumer_manager.setAssociations(
            org.openid4java.consumer.InMemoryConsumerAssociationStore())
        self.consumer_manager.setNonceVerifier(
            org.openid4java.consumer.InMemoryNonceVerifier(5000))

    def database_connection(self):
        return self.data_source.getConnection()
        
    def transformer(self, template=None):
        if template is None:
            return self.transformer_factory.newTransformer()
        else:
            return self.transformer_factory.newTransformer(template)

class JystError(Exception):
    def __init__(self, message=None, document=None):
        Exception.__init__(self)
        #self.tback = traceback.format_exc()
        if message is None:
            self.message = None
        else:
            self.message = str(message)
        self.document = document
        
    def make_document(self, empty_document):
        if self.document == None:
            self.document = empty_document
        if self.message != None:
            root_element = self.document.getDocumentElement()
            message_element = self.document.createElement('message')
            root_element.appendChild(message_element)
            text_node = self.document.createTextNode(self.message)
            message_element.appendChild(text_node)
        return self.document
        
class UserError(JystError):
    def __init__(self, message=None, document=None):
        JystError.__init__(self, message, document)
        
class NotFoundError(JystError):
    def __init__(self, message=None, document=None):
        JystError.__init__(self, message, document)
        
class InternalServerErrorError(JystError):
    def __init__(self, message=None, document=None):
        JystError.__init__(self, message, document)
 
class MovedPermanentlyError(JystError):
    def __init__(self, location, message=None, document=None):
        JystError.__init__(self, message, document)
        self.location = location
        
class Static(Urlable):
    def __init__(self):
        Urlable.__init__(self, None)

    def http_get(self, inv):
        uri = '/WEB-INF/' + inv.static_element + inv.request.getPathInfo()
        if uri[-1:] == '/':
            uri = uri[:-1]
        chosen_path = None
        paths = inv.services.servlet_context.getResourcePaths(
                                        uri[:uri.rfind('/')])
        if paths is not None:
            for path in paths:
                if path[:-1] != '/' and path.startswith(uri):  
                    supp = path[len(uri):]
                    if supp[:1] == '.' and supp.find('.', 1) == -1:
                        chosen_path = path
        if chosen_path is None:
            raise NotFoundError()
        content_type = inv.services.servlet_context.getMimeType(chosen_path)
        if content_type == None:
            raise NotFoundError()
        inv.response.setHeader('Content-type', content_type)
        inv.response.setDateHeader('Expires', System.currentTimeMillis() +
                                   1000 * 60 * 60 * 24)
        inv.response.setStatus(HttpServletResponse.SC_OK)
        ostream = inv.response.getOutputStream()
        istream = inv.services.servlet_context.getResourceAsStream(chosen_path)
        is_char = istream.read()
        while is_char != -1:
            ostream.write(is_char)
            is_char = istream.read()
        ostream.flush()
        ostream.close()
        istream.close()
        
def discover_yadis_location(identifier):
    location = None
    try:
        yadis_result = YadisResolver().discover(identifier)
        if yadis_result.isSuccess():
            location = str(yadis_result.getXrdsLocation())
    except OpenIDException: #IGNORE:W0704
        pass
    return location